大家好!今天這篇主要是實作瀏覽器上的錄音與錄影功能,這邊先列出幾個會做到的目標
那麼在開始之前先來認識一下這次會使用到的 JavaScript API,重點有三個,分別為 mediaDevices
、MediaStream
與 MediaRecorder
,以下開始介紹~
mediaDevices 主要用來獲取我們連接的媒體設備,因為媒體設備需要較高的安全性,所以有幾點限制
getUserMedia 可以擷取麥克風與視訊鏡頭的 MediaStream 供顯示,詳細的設定值可以參考這裡
const constraints = { audio: true, video: true }
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
// do something...
}).catch(err => {
// do something...
})
getDisplayMedia 可以擷取螢幕畫面的 MediaStream 供顯示,可選擇分享螢幕畫面、應用程式或是網頁分頁,詳細的設定值可以參考這裡
const constraints = { audio: true, video: true }
navigator.mediaDevices.getDisplayMedia(constraints).then(stream => {
// do something...
}).catch(err => {
// do something...
})
enumerateDevices 會列出目前可使用的設備資訊
navigator.mediaDevices.enumerateDevices().then(devices => {
// do something...
}).catch(err => {
// do something...
})
getSupportedConstraints 會列出所有支援的設定屬性
navigator.mediaDevices.getSupportedConstraints().then(constraints => {
// do something...
}).catch(err => {
// do something...
})
MediaStream 是一個影片或者音訊的 Stream,可以藉由我們剛剛介紹的 mediaDevices 來擷取,其中包含了多個 Track,例如視訊鏡頭擷取了影像與聲音,那就會有兩個 Track,獲取 Track 的方法如下
mediaStream.getTracks().forEach(track => {
// do something...
})
如果要動態改變設定或是監聽某些事件基本上都是對 Track 進行操作
MediaRecorder 是一個可以記錄 MediaStream 的方法,使用方式如下
const constraints = { audio: true, video: true }
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
const options = {
audioBitsPerSecond: 128000,
videoBitsPerSecond: 2500000,
mimeType: 'video/webm'
}
const mediaRecorder = new MediaRecorder(stream, options)
mediaRecorder.addEventListener('dataavailable', e => {
// do something...
})
mediaRecorder.start()
setTimeout(() => {
mediaRecorder.stop()
}, 50000)
}).catch(err => {
// do something...
})
在停止錄製後會觸發 dataavailable
事件,並將 Blob
檔案夾在事件內,而前面的 MIME 類型比較麻煩,可以使用 MediaRecorder.isTypeSupported(mimeType)
來檢查支援度
首先我們先把需要用到的幾個按鈕跟顯示區塊給做出來,video
用來顯示視訊畫面與螢幕分享畫面,而 img
與 canvas
則是用來截圖與顯示截圖
<div>
<button id="camera">視訊鏡頭</button>
<button id="screen">螢幕畫面</button>
<button id="screenshot">截圖</button>
<button id="screenshot-download">下載截圖</button>
<button id="video-start">開始錄影</button>
<button id="video-download">停止錄影並下載</button>
<button id="recording-start">開始錄音</button>
<button id="recording-download">停止錄音並下載</button>
<button id="stop">停止</button>
</div>
<div>
<video id="video"></video>
<img id="img">
<canvas id="canvas" style="display: none;"></canvas>
</div>
這邊使用到我們用剛剛介紹到的 getUserMedia
來擷取視訊畫面
let cameraStream
const camera = document.querySelector('#camera')
const stop = document.querySelector('#stop')
const video = document.querySelector('#video')
const constraints = { audio: true, video: true }
camera.addEventListener('click', () => {
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
cameraStream = stream
video.srcObject = stream
video.play()
})
})
stop.addEventListener('click', () => {
if (cameraStream) {
cameraStream.getTracks().forEach(track => {
track.stop()
})
cameraStream = null
}
})
完成結果如下
一樣使用剛剛介紹到的 getDisplayMedia
來擷取螢幕分享畫面
let screenStream
const screen = document.querySelector('#screen')
const stop = document.querySelector('#stop')
const video = document.querySelector('#video')
const constraints = { audio: true, video: true }
screen.addEventListener('click', () => {
navigator.mediaDevices.getDisplayMedia(constraints).then(stream => {
screenStream = stream
video.srcObject = stream
video.play()
})
})
stop.addEventListener('click', () => {
if (screenStream) {
screenStream.getTracks().forEach(track => {
track.stop()
})
screenStream = null
}
})
完成結果如下
因為分享螢幕畫面或使用視訊鏡頭時要將其他設備停止運作,所以我們把共用的邏輯拆出來
let cameraStream
let screenStream
const camera = document.querySelector('#camera')
const screen = document.querySelector('#screen')
const stop = document.querySelector('#stop')
const video = document.querySelector('#video')
function stopAllStream () {
if (cameraStream) {
cameraStream.getTracks().forEach(track => {
track.stop()
})
cameraStream = null
}
if (screenStream) {
screenStream.getTracks().forEach(track => {
track.stop()
})
screenStream = null
}
}
const constraints = { audio: true, video: true }
camera.addEventListener('click', () => {
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
stopAllStream()
cameraStream = stream
video.srcObject = stream
video.play()
})
})
screen.addEventListener('click', () => {
navigator.mediaDevices.getDisplayMedia(constraints).then(stream => {
stopAllStream()
screenStream = stream
video.srcObject = stream
video.play()
})
})
stop.addEventListener('click', stopAllStream)
截圖會用到一些轉換的技巧,可以參考之前小弟寫的 圖片轉換處理,這邊我就不多做解釋了~
let screenshotBlobUrl
const screenshot = document.querySelector('#screenshot')
const screenshotDownload = document.querySelector('#screenshot-download')
const video = document.querySelector('#video')
const img = document.querySelector('#img')
const canvas = document.querySelector('#canvas')
const ctx = canvas.getContext('2d')
screenshot.addEventListener('click', () => {
if (!cameraStream && !screenStream) return
const width = video.offsetWidth
const height = video.offsetHeight
canvas.width = width
canvas.height = height
ctx.drawImage(video, 0, 0, width, height)
canvas.toBlob(blob => {
screenshotBlobUrl = window.URL.createObjectURL(blob)
img.src = screenshotBlobUrl
})
})
screenshotDownload.addEventListener('click', () => {
if (!screenshotBlobUrl) return
const downloadLink = document.createElement('a')
downloadLink.href = screenshotBlobUrl
downloadLink.download = 'fileName'
downloadLink.click()
})
現在不管使用分享螢幕還是視訊鏡頭都可以截圖也可以下載囉!
接著一樣使用剛剛介紹到的 MediaRecorder
來錄製影片與聲音
let videoMediaRecorder
let recordingMediaRecorder
const videoStart = document.querySelector('#video-start')
const videoDownload = document.querySelector('#video-download')
const recordingStart = document.querySelector('#recording-start')
const recordingDownload = document.querySelector('#recording-download')
videoStart.addEventListener('click', () => {
if (!cameraStream && !screenStream) return
const currentStream = cameraStream || screenStream
const options = {
audioBitsPerSecond: 128000,
videoBitsPerSecond: 2500000,
mimeType: 'video/webm'
}
const mediaRecorder = new MediaRecorder(currentStream, options)
videoMediaRecorder = mediaRecorder
mediaRecorder.addEventListener('dataavailable', e => {
const blob = new Blob([e.data], { type: 'video/mp4' })
const downloadLink = document.createElement('a')
downloadLink.href = window.URL.createObjectURL(blob)
downloadLink.download = 'videoName'
downloadLink.click()
})
mediaRecorder.start()
})
videoDownload.addEventListener('click', () => {
if (!videoMediaRecorder) return
videoMediaRecorder.stop()
})
recordingStart.addEventListener('click', () => {
if (!cameraStream && !screenStream) return
const currentStream = cameraStream || screenStream
const options = {
audioBitsPerSecond: 128000,
mimeType: 'audio/webm'
}
const mediaRecorder = new MediaRecorder(currentStream, options)
recordingMediaRecorder = mediaRecorder
mediaRecorder.addEventListener('dataavailable', e => {
const blob = new Blob([e.data], { type: 'audio/mp4' })
const downloadLink = document.createElement('a')
downloadLink.href = window.URL.createObjectURL(blob)
downloadLink.download = 'recordingName'
downloadLink.click()
})
mediaRecorder.start()
})
recordingDownload.addEventListener('click', () => {
if (!recordingMediaRecorder) return
recordingMediaRecorder.stop()
})
現在影音的 API 弄得相當簡單可以上手,如果不是用舊版的瀏覽器基本上都不太會有問題(有需要支援的幫QQ),做這種有畫面的東西還是相當有趣的,各位有空有可以動手玩看看啦~